package org.yeauty.service.impl;

import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.common.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tomlj.TomlParseResult;
import org.yeauty.pojo.DiscoverConfigBO;
import org.yeauty.service.MonitorService;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MonitorServiceImpl implements MonitorService {

    private static final Logger logger = LoggerFactory.getLogger(MonitorServiceImpl.class);

    private static final String DEFAULT_SERVER = "127.0.0.1:80";

    @Override
    public void updateNginxFromNacos(TomlParseResult tomlParseResult) throws IOException, InterruptedException, NacosException {

        boolean containNginxCmd = tomlParseResult.contains(NGINX_CMD);
        boolean containNacosAddr = tomlParseResult.contains(NACOS_ADDR);
        if (!containNginxCmd) {
            throw new IllegalArgumentException(NGINX_CMD + " is no such");
        }
        if (!containNacosAddr) {
            throw new IllegalArgumentException(NACOS_ADDR + " is no such");
        }

        //判断nginx的指令是否可用
        String cmd = tomlParseResult.getString(NGINX_CMD);
        if (StringUtils.isEmpty(cmd)) {
            throw new IllegalArgumentException(NGINX_CMD + " is empty");
        }
        int i = -1;
        try {
            Process process = Runtime.getRuntime().exec(cmd + " -V");
            i = process.waitFor();
        } catch (IOException | InterruptedException e) {
            throw new IllegalArgumentException(NGINX_CMD + " is incorrect");
        }
        if (i != 0) {
            throw new IllegalArgumentException(NGINX_CMD + " is incorrect");
        }

        //判断nacos地址
        String nacosAddr = tomlParseResult.getString(NACOS_ADDR);
        String namespaceID = tomlParseResult.getString(NACOS_NAMESPACE_ID);
        String nacosGroupName = tomlParseResult.getString(NACOS_GROUP_NAME);
        String username = tomlParseResult.getString(NACOS_USERNAME);
        String password = tomlParseResult.getString(NACOS_PASSWORD);


        if (StringUtils.isEmpty(nacosAddr)) {
            throw new IllegalArgumentException(NACOS_ADDR + " is empty");
        }

        Properties properties = new Properties();

        properties.put("serverAddr", nacosAddr);
        properties.put("namespace", namespaceID);
        properties.put("username", username);
        properties.put("password", password);

        NamingService namingService = NacosFactory.createNamingService(properties);

        //获取配置项
        List<DiscoverConfigBO> list = new ArrayList<>();
        Set<String> groupNames = tomlParseResult.keySet();
        groupNames.stream().filter(groupName -> !groupName.equals(NGINX_CMD) && !groupName.equals(NACOS_ADDR) && !groupName.equals(RELOAD_INTERVAL) && !groupName.equals(NACOS_USERNAME) && !groupName.equals(NACOS_PASSWORD) && !groupName.equals(NACOS_NAMESPACE_ID)).forEach(groupName -> {
            String configPath = tomlParseResult.getString(groupName + "." + NGINX_CONFIG);
            String upstream = tomlParseResult.getString(groupName + "." + NGINX_UPSTREAM);
            String mode = tomlParseResult.getString(groupName + "." + NGINX_MODE);
            String serviceName = tomlParseResult.getString(groupName + "." + NACOS_SERVICE_NAME);

            if (StringUtils.isEmpty(configPath) || StringUtils.isEmpty(upstream) || StringUtils.isEmpty(serviceName)) {
                logger.warn("group_name:{} . {} or {} or {} is empty", groupName, NGINX_CONFIG, NGINX_UPSTREAM, NACOS_SERVICE_NAME);
                return;
            }
            DiscoverConfigBO discoverConfigBO = new DiscoverConfigBO(configPath, upstream, serviceName, mode);
            list.add(discoverConfigBO);
            logger.info("add config success , group_name:{} ", groupName);
        });

        if (list.size() == 0) {
            throw new IllegalArgumentException(NGINX_CONFIG + "," + NGINX_UPSTREAM + "," + NACOS_SERVICE_NAME + " are at least one group exists ");
        }

        // 开始监听nacos
        for (DiscoverConfigBO configBO : list) {

            // 开启Grpc监听Nacos服务列表
            namingService.subscribe(configBO.getServiceName(), nacosGroupName, event -> {
                try {
                    List<Instance> instances = namingService.getAllInstances(configBO.getServiceName(), nacosGroupName);
                    System.out.println("实例：");
                    System.out.println(instances);
                    // 更新nginx中的upstream
                    boolean updated = refreshUpstream(instances, configBO);
                    // 判断是否更新配置文件成功
                    if (updated) {
                        logger.info("upstream:{} update success!", configBO.getServiceName());
                        // 平滑重启Nginx
                        reloadNginx(cmd);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
    }

    private void reloadNginx(String cmd) {
        // 开启线程后台平滑重启Nginx
        new Thread(() -> {
            Process process = null;
            boolean result = false;
            try {
                // 判断脚本是否可用
                process = Runtime.getRuntime().exec(cmd + " -t");
                result = process.waitFor(10, TimeUnit.SECONDS);
                if (!result) {
                    logger.error("nginx timeout , execute [{}] to get detail ", (cmd + " -t"));
                    return;
                }
                if (process.exitValue() != 0) {
                    logger.error("nginx syntax incorrect , execute [{}] to get detail ", (cmd + " -t"));
                    return;
                }
                // Nginx 重启
                process = Runtime.getRuntime().exec(cmd + " -s reload");
                result = process.waitFor(10, TimeUnit.SECONDS);
                if (!result) {
                    logger.error("nginx timeout , execute [{}] to get detail ", (cmd + " -t"));
                    return;
                }
                if (process.exitValue() != 0) {
                    logger.error("nginx reload incorrect , execute [{}] to get detail ", (cmd + " -s reload"));
                    return;
                }
                logger.info("nginx reload success!");
            } catch (Exception e) {
                logger.error("reload nginx throw exception", e);
            }
        }, "reload-nginx").start();
    }

    private boolean refreshUpstream(List<Instance> instances, DiscoverConfigBO configBO) {

        String nginxUpstream = configBO.getUpstream();
        String nginxConfigPath = configBO.getConfigPath();
        String mode = configBO.getMode();
        //获取到upstream
        Pattern pattern = Pattern.compile(UPSTREAM_REG.replace(PLACEHOLDER, nginxUpstream));
        //判断文件是否存在
        File file = new File(nginxConfigPath);
        if (!file.exists() || !file.isFile()) {
            throw new IllegalArgumentException("file : " + nginxConfigPath + " is not exists or not a file");
        }
        Long length = file.length();
        byte[] bytes = new byte[length.intValue()];
        try (FileInputStream fileInputStream = new FileInputStream(file)) {
            fileInputStream.read(bytes);
        } catch (IOException e) {
            e.printStackTrace();
        }

        //获取到配置文件内容
        String conf = new String(bytes);
        //匹配对应的upstream
        Matcher matcher = pattern.matcher(conf);
        if (matcher.find()) {
            String formatSymbol = "";
            String oldUpstream = matcher.group();
            //计算出旧的upstream到左边的距离
            int index = conf.indexOf(oldUpstream);
            while (index != 0 && (conf.charAt(index - 1) == ' ' || conf.charAt(index - 1) == '\t')) {
                formatSymbol += conf.charAt(index - 1);
                index--;
            }

            //拼接新的upstream
            String newUpstream = UPSTREAM_FOMAT.replace(PLACEHOLDER, nginxUpstream);
            StringBuffer servers = new StringBuffer();
            if (instances.size() > 0) {
                // 判断是不是ip_hash
                if ("ip_hash".equals(mode)) {
                    servers.append(formatSymbol + "    ip_hash;\n");
                }

                // 判断是不是least_conn模式
                if ("least_conn".equals(mode)) {
                    servers.append(formatSymbol + "    least_conn;\n");
                }

                // fair 集群模式
                if ("fair".equals(mode)) {
                    servers.append(formatSymbol + "    fair;\n");
                }

                // url_hash 集群模式
                if ("url_hash".equals(mode)) {
                    servers.append(formatSymbol + "    hash $request_uri;\n");
                }

                for (Instance instance : instances) {
                    //不健康或不可用的跳过
                    if (!instance.isHealthy() || !instance.isEnabled()) {
                        continue;
                    }
                    String ip = instance.getIp();
                    int port = instance.getPort();
                    // 判断集群模式为权衡
                    String weight = "";
                    if ("weight".equals(mode)) {
                        weight = " weight=" + (int) Math.round(instance.getWeight());
                    }
                    servers.append(formatSymbol + "    server " + ip + ":" + port + "" + weight + ";\n");
                }
            }
            if (servers.length() == 0) {
                //如果没有对应的服务，使用默认的服务防止nginx报错
                servers.append(formatSymbol + "    server " + DEFAULT_SERVER + ";\n");
            }
            servers.append(formatSymbol);
            newUpstream = newUpstream.replace(PLACEHOLDER_SERVER, servers.toString());
            if (oldUpstream.equals(newUpstream)) {
                return false;
            }
            //替换原有的upstream
            conf = matcher.replaceAll(Matcher.quoteReplacement(newUpstream));
        } else {
            throw new IllegalArgumentException("can not found upstream:" + nginxUpstream);
        }
        try (FileWriter fileWriter = new FileWriter(file, false)) {
            fileWriter.write(conf);
            fileWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }
}
